/******************************************************************************
BonusBingoGuts.cpp: implementation of the BonusBingoGuts class.

  Copyright 2005 Realtime Gaming.  All rights reserved.
******************************************************************************/

#include "BonusBingoGuts.h"
#include "GameGuts.h"
#include "GameConnection.h"
#include "kenoserv.h"

#include "BonusBingoCard.h"
#include "BonusBingoGame.h"
#include "BonusBingoGameResult.h"
#include "BonusBingoSubGame.h"
#include "BonusBingoSQL.h"
#include "subgamedata.h"

#include "log.h"
#include "opts.h"

#include <algorithm>

/******************************************************************************
Static Initialization
******************************************************************************/
const uint8 BonusBingoGuts::MaxCards = 4;
const uint8 BonusBingoGuts::MaxGames = 1;

/******************************************************************************
Construction/Destruction
******************************************************************************/
BonusBingoGuts::BonusBingoGuts(GameConnection* connection)
    : super( connection )
{
    m_game = NULL;
    m_subGame = NULL;
    m_gameResult = NULL;
	m_tracker = new BonusBingoSQL( g_tracker );
}

BonusBingoGuts::~BonusBingoGuts()
{
    delete m_game;
    delete m_subGame;
    delete m_gameResult;
	delete m_tracker;
}

int32   
BonusBingoGuts::getMinBet() const
{
    return MinBet;
}

int32   
BonusBingoGuts::getMaxBet() const
{
    return MaxBet;
}

uint8   
BonusBingoGuts::getMaxCards() const
{
    return MaxCards;
}

uint8   
BonusBingoGuts::getMaxGames() const
{
    return MaxGames;
}

uint8   
BonusBingoGuts::getPaytable() const
{ 
    return 0;
}

int32   
BonusBingoGuts::getDenomValue(int32 index) const
{ 
	if(index < 0 || index > 4)
		return 0;
	else
		return m_anDenomValues[index];
}

void    
BonusBingoGuts::processBet(uint8* buf, 
                           int32 len, int32 num_cards, 
                           int32 num_games)
{
    //num_games must be 1
    if( num_games != 1 )
    {
        m_connection->respondInvalidBet();
    }

    if ( num_cards < 1 || num_cards > BonusBingo::NUM_MAX_CARDS )
    {
        // Must bet between 1 and MAX cards
        m_connection->respondInvalidBet();
        return;
    }

    if ( m_game )
    {
        m_connection->respondGameInProgress();
    }
    
    int32 index = 0;

    //The per card bet
    int32 bet;
	m_connection->msgRemove(&buf[index],&bet);
	
    if ( bet < MinBet || bet > MaxBet )
    {
        //Bad bet amount
        m_connection->respondInvalidBet();
        return;
    }

    int32 balance = 0;

	if( !m_connection->trackGetBalance(&balance) )
	{
        //This is what Keno does.  I guess it's to signify a DB failure.
		m_connection->respondInvalid();
		return;
	}

    if ( balance > 0 && balance >= bet * num_cards )
    {        
        index += sizeof(bet);

        m_game = new BonusBingoGame(bet);        
        
        int nextCard( 0 );
        
        //For each card with a bet:
        do
        {
            //which card index it is;
            uint32 cardIndex = buf[index++];
            
            if ( cardIndex < 0 || cardIndex > BonusBingo::NUM_MAX_CARDS )
            {
                //This is not a valid cardIndex
                m_connection->respondInvalidBet();
                return;
            }
            
            //a set of numbers indicating the matrix of numbers on the card
            BonusBingoCard* pCard = new BonusBingoCard();
            
            pCard->setValues( &buf[index] );
            
            if ( !pCard->isValid() )
            {
                m_connection->respondBadCard(len);
                return;
            }
            
            m_game->addCard( cardIndex, pCard );
            
            index += ( BonusBingoCard::NUM_BINGO_PICKS );

            pCard->activate( buf[index++] != 0 );
        }
        while ( ++nextCard < BonusBingo::NUM_MAX_CARDS );

        //Track the new game here.
        int32 balance = 0;
        
        if ( !trackNewGame( m_game, &balance ) )
        {
            answer('E');
            return;
        }
        
        //Check for a multiplier
        if ( m_game->winsMultiplier() )
        {            
            //and send multiplier setup message
            startResponse();
            addToResponse( (uint8)'x');
            addToResponse( (uint8)m_game->getMultiplierValue() );
            endResponse();
        }
        else
        {
            //just send out game results
			if ( !playCurrentGame(balance) )
            {
                answer('E');    //TODO - Malfunction
            }
        }
    }
    else
    {
        //No money to bet with!
        m_connection->respondNoBalance();
    }
}

bool8 
BonusBingoGuts::playCurrentGame(int32 balance)
{
    //Debug Sanity checks
    if ( !m_game )
    {
        //TODO
        answer('E');
		return false;
    }

    if ( !m_game->isValid() )
    {
        //TODO
        answer('E');
		return false;
    }

	bool8 retval = false;

    //Get the game results for the current game
    //and send the results to the client
    m_gameResult = m_game->play();

	if ( m_gameResult )
	{	
        int32 payout = m_gameResult->getTotalPayout();

        balance += payout;

        retval = trackGameDone( payout, &balance, m_gameResult );
        //If we got that done, let's fix up the subgame, too
        if ( retval )
        {
            retval = trackGetSubGame( m_subGame );
                
            if ( retval )
            {                
                if ( m_game->getJokerWinValue() > 0 )
                {
                    m_subGame->incrementJokerBalls( m_game->getPerCardBet() );
                }
        
#ifdef DEBUG_KNOWNSPINS
                //Call this here, so we can alter the subgame, too
                NextOutcomeValue();
#endif                        
                //Go ahead now and set up the subgame, if needed
                if ( m_subGame->getCurrentJokerBallCount() ==
                    BONUS_ROUND_JOKERS_NEEDED )
                {
                    m_subGame->selectNumberOfRolls();

#ifdef DEBUG_KNOWNSPINS
                    //selectNumberOfRolls consumes a row from the debug dataset
                    NextOutcomeValue();
#endif        
                }
                
                retval = trackUpdateSubGame( m_subGame );
            }
        }

        if ( retval )
        {
            sendResultsMessage( *m_gameResult );
            sendSubGameMessage();
        }
        else
        {
            m_connection->respondInvalid();
        }
	}
	else
	{
		//Dang - game play still failed
		//Revert the bet
		m_connection->trackAdjustBalance( -(m_game->getTotalBet()) );	
	}
    
    gameDone();
	
	return retval;
}

bool8 BonusBingoGuts::getSavedGame()
{
    int32 result = TrackSQL::ExecFailed;

	bool8 completed = false;
	
	pInfo info = m_connection->getInfo();
    
    //Load up the main game

	if (m_tracker && info)
	{
		m_tracker->lock();

        m_game = NULL;

        m_gameResult = NULL;

        result = m_tracker->getSavedGame(info->pid, info->forMoney, info->gid, &completed, m_game, m_gameResult);

		m_tracker->unlock();
	}

    //If we loaded a game, see if we can load a sub game, too
    if( m_game )
    {
        trackGetSubGame( m_subGame );
    }

    if ( completed 
        || result != TrackSQL::NoError && m_game != NULL )
    {
        //Clean this up now, then
        delete m_game;
        m_game = NULL;
        
        if ( m_gameResult )
        {
            //Clean this up now, then
            delete m_gameResult;
            m_gameResult = NULL;
        }
    }

    bool8 sendIt = !completed || m_subGame->getCurrentJokerBallCount() > 0;

    if( result != TrackSQL::NoError && m_subGame != NULL )
    {
        //Clean this up now, then
        delete m_subGame ;
        m_subGame  = NULL;
    }

	return (bool8)((result == TrackSQL::NoError) && sendIt );
}


void BonusBingoGuts::sendGameData()
{
    ASSERT( m_game || !"No BonusBingoGame Allocated" );

    addToResponse( m_game->getPerCardBet() );

    addToResponse( m_game->getMultiplierValue() );

    addToResponse ( m_game->getMultipliedCardIndex() );

    for( int8 cardIndex = 0 ; cardIndex < BonusBingo::NUM_MAX_CARDS; ++cardIndex )
    {
        BonusBingoCard* pCard = m_game->getCard( cardIndex );

        addToResponse( pCard->isActive() );

        for( int8 col = BonusBingoCard::COLUMN_B; col <= BonusBingoCard::COLUMN_O;
                ++col )
                {
                    for( int row = 0 ; row < 5; ++row )
                    {
                        addToResponse( pCard->getValue( col, row ) );
                    }
                }
    }
}

void BonusBingoGuts::sendSavedGame()
{
    if ( !m_game )
    {
        //If we don't have a game just send the subgame
        sendSubGameMessage();
        return;
    }

    startResponse();

    addToResponse((uint8)'V');

    //Send the game
    sendGameData();

    //Tell if it was played out
    addToResponse((uint8) (m_gameResult != NULL));

    endResponse();

    //Send the subgame
    sendSubGameMessage();

    if ( m_game->winsMultiplier() && m_game->getMultipliedCardID() == 0 )
    {        
        startResponse();
        addToResponse( (uint8)'x');
        addToResponse( (uint8)m_game->getMultiplierValue() );
        endResponse();
    }
    else
    {
        delete m_gameResult;
        m_gameResult = NULL;
        
        delete m_subGame;
        m_subGame = NULL;

        delete m_game;
        m_game = NULL;
    }


}

bool8 BonusBingoGuts::allowsMultipliers() const
{
    return true;
}


bool8 BonusBingoGuts::hasSubGame() const
{
    return true;
}

void BonusBingoGuts::processMultiplierResponse(uint8* msgbuf, int32 nMsgLen)
{
    //The client has been notified that the current game
    //is eligible for a multiplier.  The response indicates
    //which card they want to bet it on.

    //Must be in a game
    //Must be in a game that's won a multiplier
    //We must be waiting on their response
    if ( ! m_game 
         || !m_game->winsMultiplier() )
    {
        m_connection->respondInvalid();
        return;
    }

    //Get the card index they've specified
    uint8 multiplierCard = msgbuf[0];

    if ( 0 > multiplierCard || multiplierCard > 3 )
    {
        m_connection->respondInvalid();
        return;
    }

    //Must be a card they've elected to play on already
    if ( m_game->getCard( multiplierCard )->isActive() )
    {
        m_game->setMultipliedCardIndex( multiplierCard );
        
        int32 balance = 0;

        if( !m_connection->trackGetBalance(&balance) )
        {
            //This is what Keno does.  I guess it's to signify a DB failure.
            m_connection->respondInvalid();
            return;
        }
        
        //All good - play it
        if ( !playCurrentGame(balance) )
        {
            answer('E');    //This is a malfunction!
        }
    }
    else
    {
        m_connection->respondInvalid();
        return;
    }
}

void 
BonusBingoGuts::sendSubGameMessage()
{
    //Just need to push all the sub game data to the client
    //They'll rebuild it, and know what to do with rendering the game   
    startResponse();

    addToResponse((uint8)'S');

    sendSubGame();

    endResponse();
}

void BonusBingoGuts::sendSubGame()
{
    if ( m_subGame )
    {
        SubGame& subGame = m_subGame->getSubGameData();
        
        uint8	clientonlydatasize = subGame.getClientOnlyDataSize();
        
        uint8	datasize = subGame.getDataCount();
        
        addToResponse(clientonlydatasize);
        
        int32 id;
        int32 data;
        
        for (uint8 index = 0; index < datasize; index++ )
        {
            if ( !subGame.isServerOnlyAt(index) )
            {
                id = subGame.getIdAt(index);
                data = subGame.getDataAt(index);
                
                addToResponse(id);
                addToResponse(data);
            }
        }
    }
}

void BonusBingoGuts::processSubGameMessage()
{
    if ( !m_subGame ) 
    {
        if ( !trackGetSubGame( m_subGame ) )
        {
            m_connection->respondInvalid();
            return;
        }
    }

    if ( m_subGame->getCurrentRoll() < m_subGame->getTotalRolls() )
    {
        if ( !m_subGame->started() )
        {
            //Just need to track that we've passed this point in the sub game
            //and record the sub game state
            m_subGame->start();
        }    
        else if (!m_subGame->finished())
        {
            m_subGame->rollDice();

#ifdef DEBUG_KNOWNSPINS
            //rollDice consumes a row from the debug dataset
            NextOutcomeValue();
#endif
        }

        if ( !trackUpdateSubGame(m_subGame) )
        {
            m_connection->respondInvalid();
        }
    }

    //And respond
    sendSubGameMessage();

    if ( m_subGame->finished() )
    {
        m_subGame->reset();
        if ( !trackUpdateSubGame(m_subGame) )
        {
            m_connection->respondInvalid();
        }
    }
}

void
BonusBingoGuts::gameDone()
{
	delete m_gameResult;
    m_gameResult = NULL;

    delete m_subGame;
    m_subGame = NULL;

    delete m_game;
    m_game = NULL;
}

void
BonusBingoGuts::sendGameResult(const BonusBingoGameResult& result)
{
    typedef BonusBingoGameResult::BallSet BallSet;

    //Needto send back
    //a) the selected balls (44 of them)
    const BallSet& balls = result.getSelectedBalls();
    for( BallSet::const_iterator it( balls.begin()), end( balls.end() );
         it != end;
         ++it)
    {
        addToResponse( *it );
    }
    
    //b) Joker bonus award
    addToResponse( result.getJokerValue() );

    //c) for each card played
    for( int cardIndex = 0 ; cardIndex < BonusBingo::NUM_MAX_CARDS ; ++cardIndex )
    {
        const BonusBingoCard* pCard = m_game->getCard( cardIndex  );
        
        //c0)  The card's index
        addToResponse( (uint8)cardIndex  );
        
        //c1)  Whether it was actually played
        addToResponse( (uint8)pCard->isActive() );
        
        //c2)  The number of wins
        int numWins = result.winsForCard( cardIndex  );
        addToResponse( (uint8)numWins );
        
        for( int win = 0 ; win < numWins ; ++win )
        {
            //c3)  Win code.
            addToResponse( (uint8)result.getWinPattern( cardIndex , win ) );
        }
                
        //c5)  Payout
        addToResponse( (int32)result.payout( cardIndex ) );
    }
}

void 
BonusBingoGuts::sendResultsMessage(const BonusBingoGameResult& result)
{
    startResponse();

    addToResponse((uint8)'B');

    sendGameResult( result );

    endResponse();
}

/******************************************************************************
Tracker Methods
******************************************************************************/
int32   
BonusBingoGuts::getPlayerLimits(const PID& pid, int32* compFactor)
{
	int32 result = TrackSQL::ExecFailed;
    if ( m_tracker )
    {
        result = m_tracker->getPlayerLimits( pid, getGameID(), getMachineID(), compFactor );
    }
    return result;
}

int32   
BonusBingoGuts::getDenominations()
{
	int32 result = TrackSQL::ExecFailed;
    if ( m_tracker )
    {
		result = m_tracker->getDenominations(m_anDenomValues);
    }

	if(result == TrackSQL::NoError)
	{
		MinBet = m_anDenomValues[0];
		MaxBet = m_anDenomValues[4];
	}

    return result;
}

bool8
BonusBingoGuts::trackNewGame(BonusBingoGame *game, int32 *Balance) const
{
	pInfo info = m_connection->getInfo();

    int32 sqlresult = TrackSQL::ExecFailed;

	if ( m_tracker )
	{
		m_tracker->lock();
		sqlresult = m_tracker->createGame( ServerOpts::casinoId, info->pid,
								           info->forMoney, info->sessionId,
								           getCompFactor(), info->gid, Balance,
								           game, true );
		m_tracker->unlock();
	}


	return (bool8)(sqlresult == TrackSQL::NoError);
}

bool8 BonusBingoGuts::trackGetSubGame(BonusBingoSubGame*& subgame) const
{
	pInfo info = m_connection->getInfo();

    int32 sqlresult = TrackSQL::ExecFailed;

	if ( m_tracker )
	{
		m_tracker->lock();
        sqlresult = m_tracker->getSavedSubGame(info->pid, info->forMoney, subgame);
		m_tracker->unlock();
	}

	return (bool8)(sqlresult == TrackSQL::NoError);
}

bool8
BonusBingoGuts::trackGameDone(int32 BalAdjust, int32* Balance, 
                              BonusBingoGameResult *results) const
{
	pInfo info = m_connection->getInfo();

    int32 sqlresult = TrackSQL::ExecFailed;

	int32 comp(0);

	if ( m_tracker )
	{
		m_tracker->lock();

		sqlresult = m_tracker->gameDone( ServerOpts::casinoId, info->pid,
							info->forMoney, info->gid,
							BalAdjust, Balance, &comp /*TODO*/,
							results );

		m_tracker->unlock();
	}
	return (bool8)(sqlresult == TrackSQL::NoError);
}

bool8
BonusBingoGuts::trackUpdateSubGame(BonusBingoSubGame* subgame) const
{
	pInfo info = m_connection->getInfo();

    int32 sqlresult = TrackSQL::ExecFailed;
    
    //Update which game is in control here...
    subgame->setGameID( info->gid );

	if ( m_tracker )
    {
        m_tracker->lock();

        sqlresult = m_tracker->updateSubGame(ServerOpts::casinoId, info->sessionId,
                                            info->forMoney, subgame);

        m_tracker->unlock();
	}

	return (bool8)(sqlresult == TrackSQL::NoError);
}

#ifdef DEBUG_KNOWNSPINS
const int32 BonusBingoGuts::Random(-2);
const int32 BonusBingoGuts::Loser(-1);

#define END 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF

#define WIN_JOKER(jokerValue) Loser, Loser, Loser, Loser, 1, jokerValue
#define WIN_MULTI_WITH_JOKER(multi, jokerValue) Loser, Loser, Loser, Loser, multi, jokerValue
#define FUNCTION(id) DEBUG_FUNCTION, id, Loser, Loser, 1, 0
#define SUBGAME_ROUNDS(a) SUBGAME, a, Loser, Loser, Loser, Loser
#define DICE(a,b,c,d,e) SUBGAME, a, b, c, d, e
#define RANDOM_DICE DICE(Random, Random, Random, Random, Random)

const int32 BonusBingoGuts::DATASET_END[BonusBingoGuts::KEY_COUNT] =
{
    END
};

const HitMask BonusBingoGuts::CustomMasks[CUSTOM_COUNT] = 
{
    RowOneHitMask | RowTwoHitMask,                                      //L2_CONTIGIUOUS,
    RowOneHitMask | RowTwoHitMask | RowThreeHitMask ,                   //L3_CONTIGIUOUS,
    RowOneHitMask | RowTwoHitMask | RowThreeHitMask | RowFourHitMask,   //L4_CONTIGIUOUS,
    RowTwoHitMask | RowFourHitMask,                                     //L2_NONCONTIGIUOUS,
    RowOneHitMask | RowThreeHitMask | RowFiveHitMask,                   //L3_NONCONTIGIUOUS,
    RowOneHitMask | RowThreeHitMask | RowFourHitMask | RowFiveHitMask,   //L4_NONCONTIGIUOUS,

    SlashHitMask - HIT_AT(4, 0),                                    //NEAR_MISS_SLASH,
    BackSlashHitMask - HIT_AT(0, 0),                                //NEAR_MISS_BACKSLASH,
    CornersHitMask - HIT_AT(0, 0),                                  //NEAR_MISS_CORNERS,
    CrossHitMask - HIT_AT(0, 0),                                    //NEAR_MISS_CROSS,
    TPatternHitMask - HIT_AT(0, 0),                                 //NEAR_MISS_T,
    PerimeterHitMask - HIT_AT(0, 0),                                //NEAR_MISS_PERIMETER,
    BingoHitMask - HIT_AT(0, 0),                                    //NEAR_MISS_BINGO,
    CustomMasks[L2_CONTIGUOUS] - HIT_AT(0,0),                       //NEAR_MISS_2L,
    CustomMasks[L3_CONTIGUOUS] - HIT_AT(0,1),                       //NEAR_MISS_3L,
    CustomMasks[L4_CONTIGUOUS] - HIT_AT(0,2),                       //NEAR_MISS_4L,

    BingoHitMask | HIT_AT(2, 1) | HIT_AT(2, 2) | HIT_AT(2, 3) 
                 | HIT_AT(1, 2) | HIT_AT(3, 2),                     //BLACK_OUT,

    HIT_AT(0,0) | HIT_AT(0,1) | HIT_AT(0,2) | HIT_AT(0,3) | HIT_AT(0,4) | 
     HIT_AT(4,0) | HIT_AT(4,1) | HIT_AT(4,2) | HIT_AT(4,3) | HIT_AT(4,4), //VERT_2_LINES,
    
    CustomMasks[VERT_2_LINES] |
     HIT_AT(2,0) | HIT_AT(2,1)| HIT_AT(2,2)| HIT_AT(2,3)| HIT_AT(2,4),    //VERT_3_LINES,

    CustomMasks[VERT_3_LINES] |
     HIT_AT(3,0) | HIT_AT(3,1)| HIT_AT(3,2)| HIT_AT(3,3)| HIT_AT(3,4)     //VERT_4_LINES,
};

const int32 BonusBingoGuts::DataSet1[][BonusBingoGuts::KEY_COUNT] =
{  
    //TEST SECTION 3
    {CrossHitMask | CustomMasks[L2_CONTIGUOUS] | TPatternHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {CrossHitMask | CustomMasks[L3_CONTIGUOUS],                  //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     0},                                //Joker Value
    {CrossHitMask | CustomMasks[L4_CONTIGUOUS],                    //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     0},                                //Joker Value
    {SlashHitMask | CornersHitMask | CustomMasks[L2_CONTIGUOUS],    //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     0},                                //Joker Value
    {SlashHitMask | TPatternHitMask | CustomMasks[L3_CONTIGUOUS] ,   //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     0},                                //Joker Value
    {SlashHitMask | PerimeterHitMask,                                //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     0},                                //Joker Value
    {SlashHitMask | CustomMasks[L4_CONTIGUOUS],                     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     0},                                //Joker Value
    {CustomMasks[L2_CONTIGUOUS] | BingoHitMask,                      //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     0},                                //Joker Value
    {CustomMasks[L2_CONTIGUOUS] | PerimeterHitMask,                      //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     0},                                //Joker Value
    {CustomMasks[L2_CONTIGUOUS] | BingoHitMask,                      //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     0},                                //Joker Value
    {CustomMasks[L3_CONTIGUOUS] | PerimeterHitMask,                      //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     0},                                //Joker Value
    {CustomMasks[L4_CONTIGUOUS] | TPatternHitMask,                      //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {BingoHitMask | TPatternHitMask,    //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {TPatternHitMask | PerimeterHitMask,//Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {BackSlashHitMask | CornersHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {BackSlashHitMask | CustomMasks[L2_CONTIGUOUS] | TPatternHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {BackSlashHitMask | CustomMasks[L2_CONTIGUOUS] | PerimeterHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {BackSlashHitMask | CustomMasks[L4_CONTIGUOUS], //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {SlashHitMask | CornersHitMask,     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {CornersHitMask | CustomMasks[L2_CONTIGUOUS] | TPatternHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {CornersHitMask | CustomMasks[L3_CONTIGUOUS], //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

    {CornersHitMask | RowTwoHitMask | RowThreeHitMask | RowFourHitMask | RowFiveHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

     //TEST SECTION 4
    {CrossHitMask | SlashHitMask,       //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

    {CrossHitMask | BackSlashHitMask,   //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

    {CrossHitMask | CornersHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

    {CrossHitMask | BingoHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

    {SlashHitMask | BingoHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

    {BackSlashHitMask | BingoHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

    {CornersHitMask | BingoHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

    {CornersHitMask | PerimeterHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

    {RowOneHitMask | RowTwoHitMask | RowThreeHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

    {RowOneHitMask | RowTwoHitMask | RowFourHitMask | RowFiveHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

    {RowOneHitMask | RowTwoHitMask | RowThreeHitMask | RowFiveHitMask, //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

    {PerimeterHitMask | BingoHitMask  , //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value

    {END}
};

const int32 BonusBingoGuts::DataSet2[][BonusBingoGuts::KEY_COUNT] =
{  
    //Test Section 5 - Cycle through each win
    {SlashHitMask,                      //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {BackSlashHitMask,                  //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {CornersHitMask,                    //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {CustomMasks[L2_NONCONTIGUOUS],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {CrossHitMask,                      //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {TPatternHitMask,                   //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {CustomMasks[L3_NONCONTIGUOUS],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {PerimeterHitMask,                  //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {CustomMasks[L4_NONCONTIGUOUS],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {BingoHitMask,                      //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    {END}
};


const int32 BonusBingoGuts::DataSet3[][BonusBingoGuts::KEY_COUNT] =
{
    {CustomMasks[NEAR_MISS_SLASH],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                                 //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[NEAR_MISS_BACKSLASH],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                                 //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[NEAR_MISS_CORNERS],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                                 //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[NEAR_MISS_2L],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                                 //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[NEAR_MISS_CROSS],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                                 //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[NEAR_MISS_T],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                                 //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[NEAR_MISS_3L],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                                 //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[NEAR_MISS_PERIMETER],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                                 //Joker Value
     //0},                            //Bonus Round Count
    {CustomMasks[NEAR_MISS_4L],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                                 //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[NEAR_MISS_BINGO],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                                 //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[L2_CONTIGUOUS],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                                 //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[L2_NONCONTIGUOUS],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                                 //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[L3_CONTIGUOUS],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[L3_NONCONTIGUOUS],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[L4_CONTIGUOUS],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[L4_NONCONTIGUOUS],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[BLACK_OUT],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    /// 0},                            //Bonus Round Count
    {BingoHitMask,     //Card 1
     BingoHitMask,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    //0},                            //Bonus Round Count
    {CustomMasks[VERT_2_LINES],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[VERT_3_LINES],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    // 0},                            //Bonus Round Count
    {CustomMasks[VERT_4_LINES],     //Card 1
     Loser,                             //Card 2
     Loser,                             //Card 3
     Loser,                             //Card 4
     1,                                 //Multiplier
     Loser},                           //Joker Value
    // 0},                            //Bonus Round Count
    {END}
};

const int32 BonusBingoGuts::DataSet4[][BonusBingoGuts::KEY_COUNT] =
{
    //Test Section 1
    {WIN_JOKER(4)},
    {WIN_JOKER(5)},
    {WIN_JOKER(7)},
    {WIN_JOKER(10)},
    {WIN_JOKER(25)},
    {SUBGAME_ROUNDS(1)},
    {RANDOM_DICE},
    {WIN_JOKER(50)},

    //Test Section 2a
    //The last win will count towards us winning the first bonus round.
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {SUBGAME_ROUNDS(1)},
    {RANDOM_DICE},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {SUBGAME_ROUNDS(2)},
    {RANDOM_DICE},
    {RANDOM_DICE},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {SUBGAME_ROUNDS(3)},
    {RANDOM_DICE},
    {RANDOM_DICE},
    {RANDOM_DICE},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {SUBGAME_ROUNDS(3)},
    {DICE(1, 1, 1, 1, 1)},
    {DICE(1, 1, 1, 1, 1)},
    {DICE(1, 1, 1, 1, 1)},

    //Test Section 2b
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {SUBGAME_ROUNDS(3)},
    {DICE(1, 2, 3, 4, 5)},
    {DICE(2, 1, 3, 4, 5)},
    {DICE(3, 2, 1, 4, 5)},
    
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {SUBGAME_ROUNDS(3)},
    {DICE(4, 2, 3, 1, 5)},
    {DICE(2, 5, 3, 4, 1)},
    {DICE(1, 1, 1, 1, 1)},
    
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {SUBGAME_ROUNDS(3)},
    {DICE(1, 1, 1, 1, 5)},
    {DICE(1, 1, 1, 4, 5)},
    {DICE(1, 1, 3, 4, 5)},

    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {SUBGAME_ROUNDS(3)},
    {DICE(2, 3, 4, 1, 1)},
    {DICE(2, 3, 1, 1, 1)},
    {DICE(2, 1, 1, 1, 1)},
    
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {SUBGAME_ROUNDS(3)},
    {DICE(1, 2, 1, 2, 1)},
    {DICE(1, 1, 2, 3, 1)},
    {DICE(1, 1, 1, 4, 1)},
    
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {SUBGAME_ROUNDS(3)},
    {DICE(1, 1, 5, 1, 1)},
    {DICE(1, 4, 1, 1, 1)},
    {DICE(1, 4, 3, 1, 1)},

    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {SUBGAME_ROUNDS(3)},
    {DICE(2, 3, 4, 5, 6)},
    {DICE(2, 2, 2, 2, 2)},
    {DICE(3, 3, 3, 3, 3)},

    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {SUBGAME_ROUNDS(3)},
    {DICE(4, 4, 4, 4, 4)},
    {DICE(5, 5, 5, 5, 5)},
    {DICE(6, 6, 6, 6, 6)},
    
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {WIN_JOKER(10)},
    {SUBGAME_ROUNDS(1)},
    {DICE(6, 5, 4, 3, 2)},

    {END}
};

const int32 BonusBingoGuts::DataSet5[][KEY_COUNT] = 
{
    //TestSection 2C
    {WIN_JOKER(Random)},
    {WIN_JOKER(Random)},
    {WIN_JOKER(Random)},
    {WIN_JOKER(Random)},
    {WIN_JOKER(Random)},
    {SUBGAME_ROUNDS(3)},
    {RANDOM_DICE},
    {RANDOM_DICE},
    {RANDOM_DICE},
    {END}
};

const int32 BonusBingoGuts::DataSet6[][KEY_COUNT] = 
{
	{WIN_MULTI_WITH_JOKER(2, 4)},
	{WIN_MULTI_WITH_JOKER(2, 5)},
	{WIN_MULTI_WITH_JOKER(2, 7)},	
	{WIN_MULTI_WITH_JOKER(2, 10)},
	{WIN_MULTI_WITH_JOKER(2, 25)},
    {SUBGAME_ROUNDS(1)},
    {RANDOM_DICE},
	{WIN_MULTI_WITH_JOKER(2, 50)},
	{WIN_MULTI_WITH_JOKER(3, 4)},
	{WIN_MULTI_WITH_JOKER(3, 5)},
	{WIN_MULTI_WITH_JOKER(3, 7)},	
	{WIN_MULTI_WITH_JOKER(3, 10)},
    {SUBGAME_ROUNDS(1)},
    {RANDOM_DICE},

	{WIN_MULTI_WITH_JOKER(3, 25)},
	{WIN_MULTI_WITH_JOKER(3, 50)},
	{WIN_MULTI_WITH_JOKER(2, 7)},	
	{WIN_MULTI_WITH_JOKER(2, 10)},
	{WIN_MULTI_WITH_JOKER(2, 25)},
    {SUBGAME_ROUNDS(1)},
    {RANDOM_DICE},
    {END}
};

//The set of all data sets
const int32* BonusBingoGuts::s_DataSets[] = 
{
    &DataSet1[0][0],
    &DataSet2[0][0],
    &DataSet3[0][0],
    &DataSet4[0][0],
    &DataSet5[0][0],
    &DataSet6[0][0]
};

BonusBingoGuts::DEBUG_SPIN_PROC BonusBingoGuts::g_FunctionTable[] = 
{
    &BonusBingoGuts::clearSubGameData,
    &BonusBingoGuts::setupForSubGame
};

void BonusBingoGuts::clearSubGameData()
{
    m_subGame->reset();
    trackUpdateSubGame( m_subGame );
}

//Setup so we have 4 jokers, and the next spin can win a bonus round
void BonusBingoGuts::setupForSubGame()
{
    while( m_subGame->getCurrentJokerBallCount() < BonusBingo::BONUS_ROUND_JOKERS_NEEDED - 1 )
    {
        m_subGame->incrementJokerBalls( m_game->getPerCardBet() );
    }

    trackUpdateSubGame( m_subGame );
}

//One-based global data set number
const int32 BonusBingoGuts::s_CurrentDataSet = 1;

//Zero-based global result number
int32 BonusBingoGuts::s_CurrentOutcome = 0;

int32 BonusBingoGuts::GetOutcomeValue( int32 key )
{
    ASSERT( key < KEY_COUNT );

    return s_DataSets[s_CurrentDataSet-1][s_CurrentOutcome * KEY_COUNT + key];
}

int32 BonusBingoGuts::GetSubgameOutcomeValue( int32 key )
{   
    //Hit this assert, and it means you've asked for "SUBGAME" when you're not
    //on a subgame result
    ASSERT( SUBGAME == s_DataSets[s_CurrentDataSet-1][s_CurrentOutcome * KEY_COUNT] );

    int32 retval = s_DataSets[s_CurrentDataSet-1][s_CurrentOutcome * KEY_COUNT + (key - SUBGAME) + 1];

    return retval;
}

int32 BonusBingoGuts::NextOutcomeValue()
{
    ++s_CurrentOutcome;

    if (!memcmp( &s_DataSets[s_CurrentDataSet-1][s_CurrentOutcome * KEY_COUNT],
                 DATASET_END, 
                 sizeof( DATASET_END )))
    {
        s_CurrentOutcome = 0;
    }
    else if ( GetOutcomeValue( 0 ) == DEBUG_FUNCTION )
    {
        //Run the function,
        int32 fnID = GetOutcomeValue( 1 );

        DEBUG_SPIN_PROC p = g_FunctionTable[ fnID - DEBUG_FUNCTION ];

        (this->*p)();

        //and call us again.
        NextOutcomeValue();
    }

    return s_CurrentOutcome;
}

#endif